package View.CoolSwing;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Point;
import java.awt.Polygon;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.LinkedList;
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;

/**
 * A normal JButton which can have multiple actions added to it. When more than one action is added an arrow
 * is added to the right hand side. If the user clicks on the left, the action is performed. If the user clicks on the
 * right, a list of all of the actions is displayed.
 * @author nigel
 */
public class JMultiButton extends JButton implements MouseListener{
    /**
     * A list of all of the actions available on the button.
     */
    private LinkedList<Action> actions  = new LinkedList<Action>();
    /**
     * The action currently selected
     */
    private Action             currentAction = null;

    /**
     * Tracks if the mouse is inside the button or not.
     */
    private boolean            mouseInside = false;
    /**
     * Filled in by the paint method it records where the line is drawn and enables the fireActionPerformed method to determine if the list of options should be displayed or the current action fired
     */
    private int                boundary = 0;
    /**
     * True if all the actions are enabled, false if they are not.
     */
    private boolean            enabled = true;
    
    private Color              hoverColor = new Color(60,60,190,164);

    /**
     * Creates a new instance, no actions are added.
     */
    public JMultiButton(){
        super("GUI Editor Only");
        initialize();
    }
    
    /**
     * Creates a new instance of JComboButton, with the specified action added
     * @param action The initial action
     */
    public JMultiButton(Action action) {
        initialize();        
        addAction(action);
        setCurrentAction(action);
    }

    /**
     * Sets the button up ready for use, must be called by any constructor.
     */
    protected void initialize(){
        setHorizontalAlignment(JButton.LEFT);
        addMouseListener(this);
    }
    
    /**
     * Gets a linked list with all of the current actions in it.
     * @return A list containing all of the action.
     */
    public LinkedList<Action> getActions() {
        Object clone = actions.clone();
        
        return (LinkedList<Action>) actions.clone();
    }

    /**
     * Get the color of the rounded rectangle drawn when the mouse is hovering
     * over the arrow. 
     *
     * @return The color
     */
    public Color getHoverColor() {
        return hoverColor;
    }

    /**
     * Change the color of the rounded rectangle drawn when the mouse is hovering
     * over the arrow. 
     *
     * @param hoverColor The chosen color
     */
    public void setHoverColor(Color hoverColor) {
        this.hoverColor = hoverColor;
    }
    
    /**
     * Gets the currently active action
     * @return The currently selected action
     */
    public Action getCurrentAction() {
        return currentAction;
    }

    /**
     * Adds multiple actions to the button in a single operation
     * @param actions A linked list full of actions
     */
    public void setActions(LinkedList<Action> actions) {
        this.actions = (LinkedList<Action>) actions.clone();
        setCurrentAction(actions.getFirst());
    }
    
    /**
     * Adds an action to those available for the button.
     * @param action The action to add
     */
    public void addAction(Action action){
        actions.add(action);
        if (actions.size()==1){
            setCurrentAction(action);
        }
    }

    /**
     * Removes an action from the list of those availble from the button
     * @param action The action to remove
     */
    public void removeAction(Action action){
        actions.remove(action);
    }

    /**
     * Sets the specified action to be the current action
     * @param action The action to make current
     */
    public void setCurrentAction(Action action){
        if (actions.contains(action)){
            currentAction = action;
            super.setAction(currentAction);
            currentAction.setEnabled(this.enabled);
        };
    }

    /**
     * Overridden so that a check can be made to see if the current action should be fired, or the 
     * list of available actions should be shown.
     * @param actionEvent The action event
     */
    protected void fireActionPerformed(ActionEvent actionEvent) {
        Point p = getMousePosition();
        if (p==null){
            return;
        }
        if (p.x>boundary){
            JPopupMenu popup = new JPopupMenu();
            for (Action action : actions){
                popup.add(new ActionOptionWrapper(action));
            }
            popup.setMinimumSize(new Dimension(getWidth(),getHeight()));
            popup.show(this,0,getHeight()); 
        } else {
            super.fireActionPerformed(actionEvent);
        }
    }

    /**
     * Enables the button
     * @param enabled True if the button is enabled, false if it isn't
     */
    public void setEnabled(boolean enabled){
        this.enabled = enabled;
        super.setEnabled(enabled);
    }
    
    /**
     * Returns true if the button is enabled, false if it isn't
     * @return See above
     */
    public boolean isEnabled(){
        return this.enabled;
    }
    
    private final void debugPaintBounds(Graphics graphics){
        Rectangle inner = getInternalBounds();
        Rectangle arrow = getArrowBounds();
        
        graphics.setColor(Color.RED);
        graphics.drawRect(inner.x,inner.y,inner.x+inner.width,inner.y+inner.height);
        graphics.drawRect(arrow.x,arrow.y,arrow.x+arrow.width,arrow.y+arrow.height);
   }
    
    /** 
     * Paints the component
     *
     * @param graphics The graphics context
     */
    protected void paintComponent(Graphics graphics){
        super.paintComponent(graphics);
        //debugPaintBounds(graphics);
        Graphics2D g2d = (Graphics2D) graphics;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        boundary = getArrowBounds().x;
        drawArrow(g2d);
    }
    
    /**
     * This function can be over-ridden to draw any kind of arrow (with any kind of
     * hover effect) the over-riding class wants. 
     *
     * @param graphics The graphics context
     */
    protected void drawArrow(Graphics2D graphics){
        Rectangle   arrowBounds = getArrowBounds();
        int         hShift      = arrowBounds.width/4;
        int         vShift      = arrowBounds.height/3;

        if (!isEnabled()){
            graphics.setColor(new Color(0,0,0,128));
        } else if (mouseInside){
            graphics.setColor(hoverColor);
            graphics.fillRoundRect(arrowBounds.x+1,arrowBounds.y,arrowBounds.width,arrowBounds.height,hShift,vShift);        
            graphics.setColor(getForeground());
        } else {
            graphics.setColor(getForeground());
        }
        
        arrowBounds.x += hShift;
        arrowBounds.width -= hShift*2-1;
        arrowBounds.y += vShift;
        arrowBounds.height -= vShift*2;
        Polygon     poly = new Polygon(new int[]{arrowBounds.x, arrowBounds.x+arrowBounds.width, arrowBounds.x+arrowBounds.width/2}
                                     , new int[]{arrowBounds.y,arrowBounds.y,arrowBounds.y+arrowBounds.height},3);

        graphics.fillPolygon(poly);

    }
    
    /**
     * Gets the internal area of the button, useful for determining the draw
     * size of the seperator etc. 
     *
     * @return The internal bounds in component co-ordinates
     */
    private Rectangle getInternalBounds(){
        Insets insets = getInsets();
        Rectangle rect = new Rectangle(insets.left,insets.top,getWidth()-(insets.left+insets.right+1),getHeight()-(insets.top+insets.bottom+1));
        
        return rect;
    }
    
    /** 
     * Gets the area (in component co-ordinates) of the selection image used
     * to bring up the choices for the options
     *
     * @return The area of the arrow bounds box
     */
    private Rectangle getArrowBounds(){
        Rectangle  rect = getInternalBounds();
        
        rect.x=rect.x+(rect.width-rect.height)+1;
        rect.width=rect.height;
        
        return rect;
    }

    /**
     * Returns a preferred size a bit bigger than it needs to be so there is room
     * for the arrow. 
     *
     * @return The minimum width and height of the button
     */
    public Dimension getPreferredSize() {
        return adjustDimension(super.getPreferredSize(),true);
    }
    
    /**
     * Utilitity method allowing the upsizing or downsizing of a dimension 
     * by the amount requied by the arrow
     *
     * @param current The dimension to adjust
     * @param enlarge Upsize or downsize
     *
     * @return The adjusted dimension
     */
    protected Dimension adjustDimension(Dimension current, boolean enlarge){
        if (enlarge){
            current.width += getInternalBounds().height+1;
        } else {
            current.width -= getInternalBounds().height+1;
        }
        
        return current;
    }
    
    /**
     * Returns a minimum size a bit bigger than it needs to be so there is room
     * for the arrow. 
     *
     * @return The minimum width and height of the button
     */
    public Dimension getMinimumSize() {
        return adjustDimension(super.getMinimumSize(),true);
    }

    /**
     * Ignored
     * @param mouseEvent The mouse event
     */
    public void mouseClicked(MouseEvent mouseEvent) {
    }

    /**
     * Ignored
     * @param mouseEvent The mouse event
     */
    public void mousePressed(MouseEvent mouseEvent) {
    }

    /**
     * Ignored
     * @param mouseEvent The mouse event
     */
    public void mouseReleased(MouseEvent mouseEvent) {
    }

    /**
     * Records the mouse is inside the area
     * @param mouseEvent The mosue event
     */
    public void mouseEntered(MouseEvent mouseEvent) {
        mouseInside=true;
        repaint();
    }

    /**
     * Records the mouse has moved outside
     * @param mouseEvent The mouse event
     */
    public void mouseExited(MouseEvent mouseEvent) {
        mouseInside=false;
        repaint();
    }
    
    
    /**
     * A wrapper for the actions to be dislayed when the pop-up is drawn.
     */
    protected class ActionOptionWrapper extends AbstractAction{
        Action      internalAction = null;
        
        /**
         * Creates a new instance of a wrapper action
         * @param wrapAction The action to be wrapped
         */
        public ActionOptionWrapper(Action wrapAction){
            internalAction= wrapAction;
            putValue(Action.NAME, wrapAction.getValue(Action.NAME));
            putValue(Action.SMALL_ICON, wrapAction.getValue(Action.SMALL_ICON));
        }
        
        /**
         * Fired when the action is performed
         * @param actionEvent The action event
         */
        public void actionPerformed(ActionEvent actionEvent) {
            setCurrentAction(internalAction);
            internalAction.actionPerformed(actionEvent);
        }
        
    }
//    
//    public static void main(String[] args) {
//    	JFrame jf = new JFrame("sd");
//    	jf.add(new JMultiButton());
//    	jf.pack();
//    	jf.setVisible(true);
//    	jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//    }
}


